package io.github.luons.mediator.dsl.aspect;

import io.github.luons.mediator.core.constant.Constant;
import io.github.luons.mediator.core.enums.Operator;
import io.github.luons.mediator.core.utils.JacksonUtils;
import io.github.luons.mediator.dsl.handler.RequestHolder;
import io.github.luons.mediator.dsl.utils.MediatorUtils;
import io.github.luons.mediator.dsl.view.FilterGroupsRequest;
import io.github.luons.mediator.dsl.view.filter.FilterGroup;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Component;
import org.springframework.util.CollectionUtils;

import javax.servlet.http.HttpServletRequest;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.*;
import java.util.stream.Collectors;

/**
 * QueryFilter
 *
 * @author : luons
 */
@Slf4j
@Aspect
@Component
@Order(1)
public class QueryFilterAspect {

    @Around(value = "@annotation(io.github.luons.mediator.dsl.aspect.QueryFilter)")
    public Object queryFilterAspect(ProceedingJoinPoint point) throws Throwable {
        HttpServletRequest request = MediatorUtils.getHttpServletRequest();
        if (Objects.isNull(request)) {
            return point.proceed();
        }
        Signature signature = point.getSignature();
        if (!(signature instanceof MethodSignature)) {
            return point.proceed();
        }
        MethodSignature methodSignature = (MethodSignature) signature;
        QueryFilter annotation = methodSignature.getMethod().getAnnotation(QueryFilter.class);
        // support no json Request
        if (!isRequestJson(request)) {
            RequestHolder.addFilterGroupsRequest(convertRequest2Filter(annotation.value(), request));
            return point.proceed();
        }
        // support json Request
        String body = getBodyByInputStream(request.getInputStream());
        log.debug("############ get request body = {}", body);
        if (StringUtils.isBlank(body)) {
            return point.proceed();
        }
        FilterGroupsRequest filterGroup = JacksonUtils.parse(body, FilterGroupsRequest.class);
        if (Objects.isNull(filterGroup)) {
            return point.proceed();
        }
        // setup pageable
        MediatorUtils.setPageable(filterGroup.getPageNumber(), filterGroup.getPageSize());
        // filterGroup
        RequestHolder.addFilterGroupsRequest(assemblyFilterGroup(annotation.value(), filterGroup));
        return point.proceed();
    }

    private boolean isRequestJson(HttpServletRequest request) {
        if (Objects.isNull(request) || Objects.isNull(request.getContentType())) {
            return false;
        }
        return request.getContentType().equals(MediaType.APPLICATION_JSON_VALUE) ||
                request.getContentType().equals(MediaType.APPLICATION_JSON_UTF8_VALUE);
    }

    private String getBodyByInputStream(InputStream inputStream) throws IOException {
        if (Objects.isNull(inputStream)) {
            return null;
        }
        StringBuilder stringBuilder = new StringBuilder();
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        char[] charBuffer = new char[128];
        int bytesRead;
        while ((bytesRead = bufferedReader.read(charBuffer)) > 0) {
            stringBuilder.append(charBuffer, 0, bytesRead);
        }
        return stringBuilder.toString();
    }

    private FilterGroupsRequest assemblyFilterGroup(Class<?> clazz, FilterGroupsRequest request) {
        if (Objects.isNull(request)) {
            return null;
        }
        // check orderFields
        if (StringUtils.isNotBlank(request.getOrderFields())) {
            Arrays.stream(request.getOrderFields().split(Constant.COMMA))
                    .filter(column -> !MediatorUtils.containField(clazz, column)).forEach(column -> {
                        throw new RuntimeException(column + " orderFields is error!");
                    });
        }
        if (CollectionUtils.isEmpty(request.getFilters())) {
            return request;
        }
        // check filters
        List<FilterGroup> filterGroups = new ArrayList<>();
        for (FilterGroup filter : request.getFilters()) {
            if (Objects.isNull(filter)) {
                continue;
            }
            String name = filter.getName();
            List<Object> values = filter.getValues();
            if (StringUtils.isBlank(name) || CollectionUtils.isEmpty(values)) {
                continue;
            }
            // check entity field exist
            if (!MediatorUtils.containField(clazz, name)) {
                log.error("{} query filter is not support", name);
                continue;
            }
            filterGroups.add(filter);
        }
        request.setFilters(filterGroups);
        // check dimensions

        return request;
    }

    public static FilterGroupsRequest convertRequest2Filter(Class<?> clazz, HttpServletRequest request) {

        List<FilterGroup> filterGroupList = new ArrayList<>();
        FilterGroupsRequest filterGroupsRequest = new FilterGroupsRequest();
        Enumeration<String> parameterNames = request.getParameterNames();
        while (parameterNames.hasMoreElements()) {
            String parameterName = parameterNames.nextElement();
            String[] parameterValues = request.getParameterValues(parameterName);
            if (Objects.isNull(parameterValues) || parameterValues.length == 0) {
                continue;
            }
            if (Constant.PAGE_NUMBER.equals(parameterName)) {
                filterGroupsRequest.setPageNumber(Integer.parseInt(parameterValues[0]));
                continue;
            } else if (Constant.PAGE_SIZE.equals(parameterName)) {
                filterGroupsRequest.setPageSize(Integer.parseInt(parameterValues[0]));
                continue;
            } else if ("searchColumns".equals(parameterName)) {
                filterGroupsRequest.setSearchColumns(parameterValues[0]);
                continue;
            } else if ("keyword".equals(parameterName)) {
                filterGroupsRequest.setKeyword(parameterValues[0]);
                continue;
            } else if ("orderFields".equals(parameterName)) {
                filterGroupsRequest.setOrderFields(parameterValues[0]);
                continue;
            } else if ("orderBy".equals(parameterName)) {
                filterGroupsRequest.setOrderBy(parameterValues[0]);
                continue;
            }
            // check entity field
            Class<?> fieldClazz = MediatorUtils.containFieldType(clazz, parameterName);
            if (Objects.isNull(fieldClazz)) {
                log.error("{} query filter is not support", parameterName);
                continue;
            }
            FilterGroup filterGroup = new FilterGroup();
            filterGroup.setName(parameterName);
            filterGroup.setOperator(Operator.EQ.getName());
            // convert type by entity
            filterGroup.setValues(Arrays.stream(parameterValues)
                    .map(value -> MediatorUtils.convert(value, fieldClazz)).collect(Collectors.toList()));
            filterGroupList.add(filterGroup);
        }
        filterGroupsRequest.setFilters(filterGroupList);
        return filterGroupsRequest;
    }

}
